#include "component.h"
#include "rwlock.h"
#include "matrix.h"
#include "str.h"
#include "general.h"
#include "assert.h"

/**
 * TODO: 由于组件描述符中使用原生的 dlist，这导致模块被多次安装/卸载时没有防御性检查，会重复安装/卸载导致错误，后续可以优化为 carray
 */

/* the list of components */
static mx_dlist_pt complist = mx_null;
static mx_rwlock_t rwlock;

/* the result of hash can't be 0 */
static inline mx_uint8_t get_hash (mx_cstring_pt str)
{
    /* xor algor */
    mx_uint8_t acc = 0;
    while (*str)
    {
        acc ^= *str;
        str ++;
    }
    /* remap 0x00 to 0xff */
    if (acc == 0)
    {
        acc == 0xff;
    }

    return acc;
}

/**
 * get the type of component
 * @param pcomp: the descriptor of component
 * @return: component's type
 */
mx_uint16_t mx_component_type (mx_component_pt pcomp)
{
    return pcomp->type;
}

/**
 * get the component by name
 * @param name: the component name
 * @return: the descriptor of component, null means not found
 * @note: we can't call it in component's events
 */
mx_component_pt mx_component_get (mx_cstring_pt name)
{
    assert(name == mx_null);

    mx_component_pt ret = mx_null;
    mx_uint8_t hash = get_hash(name);

    mx_rwlock_rlock(&rwlock);
    /* serch in `complist` list */
    if (complist != mx_null)
    {
        mx_dlist_foreach(complist, l, {
            mx_component_pt pcomp = container(l, mx_component_t, list);
            /* check hash first then check name string */
            if (hash == pcomp->hash && str_isequal(name, pcomp->name))
            {
                ret = pcomp;
                break;
            }
        });
    }
    mx_rwlock_unlock(&rwlock);

    return ret;
}

/**
 * mount the component to system
 * @param pcomp: the descriptor of component
 * @param name: the name of component, must be globally unique
 * @param lname: the target component name that want to link (for linker), it can be null
 * @param type: the type of component
 * @param events: the events of component (for linkee), it can be null
 * @return: whether the mount operation successful or not
 */
mx_bool_t mx_component_mount (mx_component_pt pcomp, mx_cstring_pt name,
                              mx_cstring_pt lname, mx_uint16_t type, mx_component_events_pt events)
{
    assert(name == mx_null);
    assert(lname == mx_null && events == mx_null);
    assert(events != mx_null && (events->onlinkup == mx_null || events->onlinkdown == mx_null));
    assert(str_isequal(name, lname));

    /* initialize attributes */
    pcomp->name = name;
    pcomp->type = type;

    pcomp->lname = lname;
    pcomp->linkee = mx_null;
    pcomp->events = events;
    pcomp->hash = get_hash(name);
    pcomp->lhash = get_hash(lname);

    /* call the onmount event */
    if (events->onmount != mx_null)
    {
        events->onmount(pcomp);
    }

    mx_rwlock_wlock(&rwlock);

    /* do linkup, i'm a linker, who do i want to link? */
    if (lname != mx_null && complist != mx_null)
    {
        mx_bool_t isfind_linkee = mx_false;
        mx_dlist_foreach(complist, l, {
            mx_component_pt plinkee = container(l, mx_component_t, list);
            /* check hash first and then check name string */
            if (pcomp->lhash == plinkee->hash && str_isequal(lname, plinkee->name))
            {
                assert(plinkee->events == mx_null);
                /* mark found it */
                isfind_linkee = mx_true;
                /* call the linkee's onlinkup event */
                if (plinkee->events->onlinkup(plinkee, pcomp))
                {
                    pcomp->linkee = plinkee;
                }
                break;
            }
        });

        /* check the result of linker mount operation,
           here we need exit if found linkee but linkup failed */
        if (isfind_linkee && pcomp->linkee == mx_null)
        {
            if (events->onumount != mx_null)
            {
                events->onumount(pcomp);
            }
            return mx_false;
        }
    }

    /* mount to system */
    if (complist == mx_null)
    {
        mx_dlist_init(&pcomp->list);
    }
    else
    {
        mx_dlist_insert_before(complist, &pcomp->list);
    }
    complist = &pcomp->list;    // force! for the following

    /* do linkup, i'm a linkee, who wants to link to me? */
    if (events != mx_null)
    {
        mx_dlist_foreach_removable(complist, l, {
            mx_component_pt plinker = container(l, mx_component_t, list);
            if (pcomp->hash == plinker->lhash && str_isequal(pcomp->name, plinker->lname))
            {
                if (events->onlinkup(pcomp, plinker))   // linkup successful
                {
                    plinker->linkee = pcomp;
                }
                else    // linkup failed and unmount it (first-level)
                {
                    /* it is a linker&linkee component,
                        need do unlinkup with level2-linker before unmount it */
                    if (plinker->events != mx_null)
                    {
                        mx_dlist_foreach_prev(complist, pl, {
                            mx_component_pt pl2er = container(pl, mx_component_t, list);
                            /* find the linked component */
                            if (pl2er->linkee == plinker)
                            {
                                if (plinker->events->onlinkdown != mx_null)
                                {
                                    plinker->events->onlinkdown(plinker, pl2er);
                                }
                                pl2er->linkee = mx_null;
                            }
                        });
                    }
                    /* do unmount */
                    if (plinker->events->onumount != mx_null)
                    {
                        plinker->events->onumount(plinker);
                    }
                    /* remove it from `complist` */
                    mx_dlist_remove(&plinker->list);
                }
            }
        });
    }

    mx_rwlock_unlock(&rwlock);

    return mx_true;
}

/**
 * unmount the component from system
 * @param pcomp: the descriptor of component
 * @return: void
 */
void mx_component_umount (mx_component_pt pcomp)
{
    mx_rwlock_wlock(&rwlock);

    /* do linkdown, i'm a linkee, who linked to me? */
    if (pcomp->events != mx_null && complist != mx_null)
    {
        mx_dlist_foreach(complist, l, {
            mx_component_pt plinker = container(l, mx_component_t, list);
            if (plinker->linkee == pcomp)
            {
                if (pcomp->events->onlinkdown != mx_null)
                {
                    pcomp->events->onlinkdown(pcomp, plinker);
                }
                plinker->linkee = mx_null;
            }
        });
    }

    /* do linkdown, i'm a linker, who have i linked to */
    if (pcomp->linkee != mx_null)
    {
        if (pcomp->linkee->events->onlinkdown != mx_null)
        {
            pcomp->linkee->events->onlinkdown(pcomp->linkee, pcomp);
        }
    }

    /* call unmount event */
    if (pcomp->events->onumount != mx_null)
    {
        pcomp->events->onumount(pcomp);
    }

    /* remove component from system */
    if (complist == &pcomp->list)
    {
        complist = mx_dlist_next(&pcomp->list);
        if (complist == &pcomp->list)
        {
            complist = mx_null;
        }
        else
        {
            mx_dlist_remove(&pcomp->list);
        }
    }
    else
    {
        mx_dlist_remove(&pcomp->list);
    }

    mx_rwlock_unlock(&rwlock);
}

// TODO: 实现已挂载组件的重命名操作，如果已经 linkup 则需要断开链接，去匹配新的组件
void mx_component_rename (mx_component_pt pcomp);   // 修改 name
void mx_component_relname (mx_component_pt pcomp);  // 修改 lname

static void component_init ()
{
    mx_rwlock_init(&rwlock);
}
MX_BOARDINIT_EXPORT(component_init, 0);
